package com.ioi.core;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 数据访问层通用类
 *
 * @author LILIBO
 * @since 2024/4/27
 */
public class BaseDao {

    private static final Logger log = LogManager.getLogger(BaseDao.class);

    /**
     * 数据库连接配置文件
     */
    private static final String config = "database.properties";

    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;

    /*
     * 静态代码块，类创建时执行
     */
    static {
        // 加载配置文件
        loadConfig();
        // 加载数据库驱动
        loadDriver();
    }

    /**
     * 加载配置文件
     */
    private static void loadConfig() {
        Properties properties = new Properties();
        try {
            properties.load(BaseDao.class.getClassLoader().getResourceAsStream(config));
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
        } catch (IOException e) {
            log.error(e.fillInStackTrace());
        }
    }

    /**
     * 加载数据库驱动
     */
    private static void loadDriver() {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            log.error(e.fillInStackTrace());
        }
    }

    /**
     * 获取数据库连接
     *
     * @return 数据库连接对象
     */
    public Connection getConnection() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            log.error(e.fillInStackTrace());
        }
        return conn;
    }

    /**
     * 关闭连接释放资源
     *
     * @param rs 数据库结果集对象
     * @param stmt 数据库操作对象
     * @param conn 数据库连接对象
     */
    public void closeAll(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error(e.fillInStackTrace());
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                log.error(e.fillInStackTrace());
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                log.error(e.fillInStackTrace());
            }
        }
    }

    /**
     * 执行通用增删改
     *
     * @param sql 需要执行的SQL语句
     * @param params SQL语句问号处对应的参数
     * @return 执行该语句影响的行数
     */
    public int executeUpdate(String sql, Object... params) {
        log.debug("{}\n{}", sql, Arrays.toString(params));
        // 返回受影响的行数
        int row = 0;
        // 获取数据库连接
        Connection conn = this.getConnection();
        PreparedStatement pstmt = null;
        try {
            // 创建数据库操作对象预处理SQL语句
            pstmt = conn.prepareStatement(sql);
            // 给SQL语句问号处设置参数
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            // 执行增删改操作返回受影响行数
            row = pstmt.executeUpdate();
            log.debug("Update : {}", row);
        } catch (SQLException e) {
            log.error(e.fillInStackTrace());
        } finally {
            // 关闭连接释放资源
            this.closeAll(null, pstmt, conn);
        }
        return row;
    }

    /**
     * 执行通用统计值
     *
     * @param sql 需要执行的SQL语句
     * @param params SQL语句问号处对应的参数
     * @return 执行该语句影响的行数
     */
    public int executeCount(String sql, Object... params) {
        log.debug("{}\n{}", sql, Arrays.toString(params));
        // 返回统计的数量
        int count = 0;
        // 获取数据库连接
        Connection conn = this.getConnection();
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 创建数据库操作对象预处理SQL语句
            pstmt = conn.prepareStatement(sql);
            // 给SQL语句问号处设置参数
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            // 执行增删改操作返回受影响行数
            rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
            log.debug("Count : {}", count);
        } catch (SQLException e) {
            log.error(e.fillInStackTrace());
        } finally {
            // 关闭连接释放资源
            this.closeAll(rs, pstmt, conn);
        }
        return count;
    }

    /**
     * 执行通用查询
     *
     * @param targetType 查询封装的目标类型（查询封装到哪个类）
     * @param sql 预编译的SQL语句（可能会带?占位符）
     * @param params 参数列表（可变长参数，与?个数一致）
     * @return 封装好的数据对象集合
     */
    public <T> List<T> executeQuery(Class<T> targetType, String sql, Object... params) {
        log.debug("{}\n{}", sql, Arrays.toString(params));
        List<T> list = new ArrayList<>(); // 最终返回的结果
        Connection conn = this.getConnection(); // 获得数据库连接
        PreparedStatement pstmt = null; // 初始化数据库操作对象
        ResultSet rs = null; // 查询结果集
        try {
            pstmt = conn.prepareStatement(sql); // 获得数据库操作对象
            if (params != null) { // 为SQL语句设置参数，多少个问号"?"就设置多少个参数一一对应
                for (int i = 0; i < params.length; i++) {
                    if (params[i] != null) {
                        pstmt.setObject(i + 1, params[i]); // 循环给问号的位置（从1开始）依次设置参数值
                    }
                }
            }
            // 执行查询
            rs = pstmt.executeQuery();

            // ------ 原来的实现方式，从结果集中读取到一行数据就实例化一个对象，取出列数据针对性的给对象属性赋值 ------

			/*
			// 处理结果集中的数据
			while (rs.next()) {
				Student student = new Student(); // 有一行数据就创建一个学生对象
				student.setId(rs.getInt(1));
				student.setName(rs.getString(2));
				student.setSex(rs.getInt(3));
				student.setAddress(rs.getString(4));
				student.setBirthday(rs.getString(5));
				list.add(student); // 将对象添加到集合
			}
			*/

            // ------ 通过反射实现通用的数据列与实体映射赋值 ------

            // 结果集列信息Map集合
            Map<String, String> columnMap = new HashMap<>();
            // 获得ResultSet对象的列的数量、类型和属性
            ResultSetMetaData metaData = rs.getMetaData();
            // 获得查询结果集总列数
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                // 将列名及对应的类型信息保存到Map集合
                columnMap.put(metaData.getColumnName(i), metaData.getColumnTypeName(i));
            }

            // 处理结果集中的数据
            while (rs.next()) {
                // 通过反射，使用Class<T>构造一个对象
                T target = targetType.newInstance();
                // 获取Class<T>里面所有的public方法
                Method[] methods = targetType.getMethods();
                for (Method method : methods) {
                    // 找到以set开头的方法
                    if (method.getName().startsWith("set")) {
                        // 获取方法的参数列表
                        Class<?>[] args = method.getParameterTypes();
                        // 找到只有一个参数的方法
                        if (args.length == 1) {
                            // 获取对象属性的set方法，将结果集中的列对应赋值给属性
                            // setUserName -> userName
                            String paramName = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
                            // 查看所有参数的Map中是否存在key和对象的属性匹配的
                            if (columnMap.containsKey(paramName)) {
                                try {
                                    // 将String类型的字段数据转换为对应实体属性类型
                                    Object value = parseValue(rs.getString(paramName), args[0]);
                                    // 将查询结果的字段值与实体类型属性通过setXXX()方法进行映射绑定（通过反射使用目标对象target调用method方法，传递一个参数value）
                                    method.invoke(target, value);
                                } catch (ParseException e) {
                                    log.error(e.fillInStackTrace());
                                    throw e;
                                }
                            } else if (Boolean.class == args[0] || boolean.class == args[0]) {
                                // 如果是boolean类型，不存在表示false
                                method.invoke(target, false);
                            }
                        }
                    }
                }

                // 将对象添加到集合
                list.add(target); // 将通过反射创建的对象添加到集合
            }
            log.debug("Result : (count = {})", list.size());
        } catch (SQLException | InstantiationException | InvocationTargetException | IllegalAccessException | ParseException e) {
            log.error(e.fillInStackTrace());
        } finally {
            // 关闭连接释放资源
            this.closeAll(rs, pstmt, conn);
        }
        return list;
    }

    /**
     * 将String类型的参数值转换为相关属性的类型
     *
     * @param value 要转换的数据
     * @param type 对应的类型
     * @return
     */
    private Object parseValue(String value, Class type) throws ParseException {
        if (value == null) {
            return null;
        }
        if (String.class == type) {
            return value;
        }
        if (boolean.class == type || Boolean.class == type) {
            if (value.isEmpty() || "false".equals(value) || "0".equals(value)) {
                return false;
            }
            return true;
        }
        if (java.util.Date.class == type) {
            return new SimpleDateFormat("yyyy-MM-dd").parse(value);
        }
        if (char.class == type || Character.class == type) {
            return value.charAt(0);
        }
        if (int.class == type || Integer.class == type) {
            return Integer.valueOf(value); // value = "1" --> 1
        }
        if (byte.class == type || short.class == type || long.class == type || float.class == type || double.class == type) {
            try {
                // byte | Byte -> java.lang.Byte
                type = Class.forName("java.lang." + type.getName().substring(0, 1).toUpperCase() + type.getName().substring(1));
                if (type.getName().startsWith("java.lang.") || type.getName().startsWith("java.math.")) {
                    // long l = new Long("9"); // 9
                    // 获取参数类型的构造函数（带一个String参数）
                    return type.getConstructor(String.class).newInstance(value);
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                log.error(e.fillInStackTrace());
            }
        }
        return null;
    }

}
